/*
Copyright 2010,2011 Matt Van Der Westhuizen

This file is part of CGLL.

CGLL is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

CGLL is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with CGLL.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.chaoticengine.cgll.entity;

import java.util.ArrayList;
import java.util.List;
import org.chaoticengine.cgll.entity.component.ComponentManager;
import org.chaoticengine.cgll.entity.component.IComponent;
import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.geom.Shape;
import org.newdawn.slick.geom.Vector2f;
import org.simpleframework.xml.Attribute;
import org.simpleframework.xml.Element;
import org.simpleframework.xml.ElementList;
import org.simpleframework.xml.Root;

/**
 * @author Matt v.d. Westhuizen
 */
@Root(name="entity")
public class Entity implements IEntity {
    public static enum EntityType {
        NONE, // This entity is nothing special
        PLAYER, // This entity is the player avatar        
        ENEMY, // This entity is an antagonist
        BULLET, // This entity is a bullet - bullets hit ENEMY & PLAYER but not other BULLETs
        DESTRUCTIBLE_BULLET, // This is a bullet that can be destroyed by other bullets
        BUILDING, // Buildings are generally collidable and destructible and can belong to either side
    };

    /** Unique identifier for this entity. */
    private int id = 0;

    /** Name of the entity - should be set by factory loading it. */
    private String name = "";

    /** Reference to the EntityManager that owns this entity. */
    private EntityManager owner = null;

    /** ComponentManager for controlling components of this entity. */
    private ComponentManager cm = new ComponentManager(this);

    /** The high-level type of the entity. */
    private EntityType type = EntityType.NONE;

    /** The entity position (center of the entity). */
    private Vector2f position = new Vector2f();

    /** The entity translation (if any). */
    private Vector2f translation = new Vector2f();

    /** The entity rotation in degrees. */
    private float rotation = 0.0f;
    
    /** The front of the entity, expressed as a normalised vector. */
    private Vector2f direction = new Vector2f(0.0f, -1.0f);

    /** The bounding rectangle of the entity. */
    private Shape boundShape = new Rectangle(0.0f, 0.0f, 0.0f, 0.0f);

    /** Is the entity moving at the moment. */
    private boolean moving = false;

    private float alpha = 1.0f;

    /** Should this entity be rendered. */
    private boolean visible = true;

    /** List of objects to notify when this entity is destroyed. */
    private List<DestroyListener> destroyListeners = new ArrayList<DestroyListener>();

    public Entity() {
    }

    @Attribute(name="id", required=false)
    public int getId() {
        return (id);
    }

    @Attribute(name="id", required=false)
    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public ComponentManager getComponentManager() {
        return (this.cm);
    }

    @ElementList(name="components", entry="component", required=false)
    public List<IComponent> getComponents() {
        return(cm.getComponents());
    }

    @ElementList(name="components", entry="component", required=false)
    public void setComponents(List<IComponent> comps) {
        cm.setComponents(comps);
        if (this.owner != null) {
            this.owner.updateCapabilities(this);
        }
    }

    @Element(name="position", required=false)
    public Vector2f getPosition() {
        return (this.position.copy());
    }

    @Element(name="position", required=false)
    public void setPosition(Vector2f position) {
        this.position = position;

        // Update bounding rectangle
        this.boundShape.setCenterX(position.x);
        this.boundShape.setCenterY(position.y);
    }

    /**
     * @param owner the owner to set
     */
    public void setOwner(EntityManager owner) {
        this.owner = owner;
    }

    /**
     * @return the owner
     */
    public EntityManager getOwner() {
        return owner;
    }

    /**
     * @param translation the translation to set
     */
    @Element(name="translation", required=false)
    public void setTranslation(Vector2f translate) {
        this.translation = translate;
    }

    /**
     * @return the translation
     */
    @Element(name="translation", required=false)
    public Vector2f getTranslation() {
        return translation.copy();
    }

    /**
     * @param rotation the rotation to set
     */
    public void setRotation(float rotation) {
        this.rotation = rotation;
        this.direction = new Vector2f(0.0f, -1.0f);
        this.direction.setTheta(rotation + 270);
    }

    /**
     * @return the rotation
     */
    public float getRotation() {
        return rotation;
    }

    /**
     * @return the current facing direction
     */
    @Element(name="direction", required=false)
    public Vector2f getDirection() {
        return(direction.copy());
    }

    /**
     * @param dir the direction to set
     */
    @Element(name="direction", required=false)
    public void setDirection(Vector2f dir) {
        this.rotation = (float) dir.getTheta() - 270;
        this.direction = new Vector2f(0.0f, -1.0f);
        this.direction.setTheta(this.rotation + 270);
    }

    /**
     * @param rect the bounding rectangle to set
     */
    @Element(name="bounds", required=false)
    public void setBounds(Shape shape) {
        this.boundShape = shape;
    }

    /**
     * @return the bounding rectangle
     */
    @Element(name="bounds", required=false)
    public Shape getBounds() {
        return(boundShape);
    }

    @Element(name="type", required=false)
    public EntityType getType() {
        return type;
    }

    @Element(name="type", required=false)
    public void setType(EntityType type) {
        this.type = type;
    }

    public boolean isMoving() {
        return moving;
    }

    public void setMoving(boolean moving) {
        this.moving = moving;
    }

    public float getAlpha() {
        return alpha;
    }

    public void setAlpha(float alpha) {
        if (alpha > 1.0f) {
            alpha = 1.0f;
        } else if (alpha < 0.0f) {
            alpha = 0.0f;
        } else {
            this.alpha = alpha;
        }
    }

    public boolean isVisible() {
        return visible;
    }

    public void setVisible(boolean visible) {
        this.visible = visible;
    }
    
    /**
     * Destroys this entity.
     */
    public void destroy() {
        for (int i = 0; i < destroyListeners.size(); i++) {
            destroyListeners.get(i).onDestroy(this, null);
        }

        getOwner().destroyEntity(this);
    }

    /**
     * Destroys this entity and specifies which entity was responsible for the
     * destruction.
     */
    public void destroy(Entity destroyer) {
        for (int i = 0; i < destroyListeners.size(); i++) {
            destroyListeners.get(i).onDestroy(this, destroyer);
        }

        getOwner().destroyEntity(this);
    }

    public void addDestroyListener(DestroyListener listener) {
        if (!this.destroyListeners.contains(listener)) {
            this.destroyListeners.add(listener);
        }
    }

    public void removeDestroyListener(DestroyListener listener) {
        this.destroyListeners.remove(listener);
    }

    public void removeAllListeners() {
        this.destroyListeners.clear();
    }

    public interface DestroyListener {
        public void onDestroy(Entity e, Entity destroyer);
    }
}
